﻿using Castle.DynamicProxy;
using System;
using System.Reflection;
using System.Threading.Tasks;
using Yto.Common.Reflection;

namespace Yto.Unitwork
{
    public class UnitworkInterceptor : IInterceptor
    {
        private IUnitManager unitManager;

        public UnitworkInterceptor(IUnitManager manager)
        {
            unitManager = manager;
        }
        public void Intercept(IInvocation invocation)
        {
            var unitwAttribute = UnitworkAttribute.GetAttributeByMethodTarget(invocation.MethodInvocationTarget);
            if (unitwAttribute == null || unitwAttribute.IsDisposed)
            {
                invocation.Proceed();
                return;
            }

            if (invocation.Method.IsAsynchronize())
            {
                var uw = unitManager.Begin();
                try
                {
                    invocation.Proceed();
                }
                catch(Exception ex)
                {
                    uw.Dispose();
                    throw;
                }

                if(invocation.Method.ReturnType == typeof(Task))
                {
                    invocation.ReturnValue = AwaitTask(
                        (Task)invocation.ReturnValue,
                        async () => await uw.CompleteAsync(),
                        exception => uw.Dispose()
                        );
                }
                else
                {
                    invocation.ReturnValue = CallAwaitTaskWithGerneric(
                        invocation.Method.ReturnType.GenericTypeArguments[0],
                        invocation.ReturnValue,
                        async () => await uw.CompleteAsync(),
                        exception => uw.Dispose()
                        );
                }

            }
            else
            {
                using(var uw= unitManager.Begin())
                {
                    invocation.Proceed();
                    uw.Complete();
                }
            }
        }

        private object CallAwaitTaskWithGerneric(Type type, object returnValue, Func<Task> func, Action<Exception> action)
        {
            return typeof(UnitworkInterceptor).GetMethod("AwaitTaskWithGerneric",BindingFlags.NonPublic | BindingFlags.Instance)
                .MakeGenericMethod(type)
                .Invoke(this, new object[] { returnValue, func, action });
        }

        private async Task AwaitTask(Task returnValue, Func<Task> func, Action<Exception> action)
        {
            Exception exception = null;

            try
            {
                await returnValue;
                await func();
            }
            catch (Exception ex)
            {
                exception = ex;
                throw;
            }
            finally
            {
                action(exception);
            }
        }

        private async Task<T> AwaitTaskWithGerneric<T>(Task<T> funcReturnValue, Func<Task> func, Action<Exception> action)
        {
            Exception exception = null;
            try
            {
                var result = await funcReturnValue;
                await func();
                return result;
            }
            catch (Exception ex)
            {
                exception = ex;
                throw;
            }
            finally
            {
                action(exception);
            }
        }
    }
}
